package com.ttg.web.service.Impl;

import com.ttg.common.component.LoginCrypto;
import com.ttg.common.config.GatewayConfig;
import com.ttg.common.constant.BasicConstant;
import com.ttg.common.dto.OrganEncryptDto;
import com.ttg.common.enums.AdminTypeEnum;
import com.ttg.common.enums.CryptoKeyConfigTypeEnum;
import com.ttg.common.enums.ErrorCodeEnums;
import com.ttg.common.exception.BaseException;
import com.ttg.common.utils.BasicUtils;
import com.ttg.common.utils.JmSwitch;
import com.ttg.common.utils.JsonUtils;
import com.ttg.common.utils.JwtUtil;
import com.ttg.common.config.ManageConfig;
import com.ttg.common.dto.BasicDto;
import com.ttg.common.vo.OrganEncryptVo;
import com.ttg.model.pojo.Admin;
import com.ttg.model.pojo.AdminAuth;
import com.ttg.model.pojo.AdminBindRole;
import com.ttg.model.pojo.AdminRoleAuth;
import com.ttg.web.redis.RedisConstants;
import com.ttg.web.redis.RedissonUtils;
import com.ttg.common.vo.OrganCheckSignVo;
import com.ttg.web.service.*;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RBucket;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author zzq
 * @version 1.0
 * @Description
 * @date 2021/3/17 10:24
 */
@Slf4j
@Service
public class ManageAuthServiceImpl implements ManageAuthService {

    @Autowired
    private ManageConfig manageConfig;
    @Autowired
    private RedissonUtils redissonUtils;
    @Autowired
    private AdminService adminService;
    @Autowired
    private AdminRoleAuthService adminRoleAuthService;
    @Autowired
    private AdminAuthService adminAuthService;
    @Autowired
    private AdminBindRoleService adminBindRoleService;

    @Autowired
    private GatewayConfig gatewayConfig;

    @Value("${access.path}")
    private String accessPath;

    @Override
    public OrganCheckSignVo checkSign(BasicDto dto) {
        String url = dto.getUrl().replaceFirst(accessPath,"");
        dto.setUrl(url);
        if (StringUtils.isBlank(dto.getToken())) {
            if (CollectionUtils.isEmpty(manageConfig.getLoginIgnoreUrl())) {
                throw new BaseException("获取系统配置异常");
            }
            boolean isAuth = Optional.ofNullable(manageConfig.getLoginIgnoreUrl()).orElse(new ArrayList<>()).stream().anyMatch(ignoreUrl -> ignoreUrl.equals(url));
            if (!isAuth) {
                throw new BaseException(ErrorCodeEnums.LOGIN_TOKEN_OVERDUE);
            }
            return checkSignByLogin(dto);
        }
        return checkSignByToken(dto);
    }

    /**
     * Token方式登录验签
     */
    @Override
    public OrganCheckSignVo checkSignByLogin(BasicDto dto) {
        LoginCrypto loginCrypto = JmSwitch.getLoginCrypto(dto.getSign_type());
        OrganCheckSignVo vo = new OrganCheckSignVo();
        String defaultAesKey = loginCrypto.getDefaultKey(CryptoKeyConfigTypeEnum.ADMIN);
        if (StringUtils.isBlank(defaultAesKey)) {
            throw new BaseException("系统默认aesKey未配置");
        }
        if (StringUtils.isBlank(dto.getData())) {
            throw new BaseException("请求参数为空");
        }
        if (StringUtils.isBlank(dto.getSign())) {
            throw new BaseException(ErrorCodeEnums.SIGN_NOT_NULL);
        }
        //验签
        boolean checkSign = loginCrypto.verifySign(dto);
        if (!checkSign) {
            log.error("验签失败：{}", JsonUtils.objectToJson(dto));
            throw new BaseException(ErrorCodeEnums.SIGN_ERROR);
        }
        //解密数据
        String data = dto.getData();
        if (StringUtils.isNotBlank(data)) {
            try {
                vo.setData(loginCrypto.decrypt(data, defaultAesKey));
            } catch (BaseException e) {
                throw e;
            } catch (Exception e) {
                log.error("系统异常: ", e);
                throw new BaseException(e.getMessage());
            }
        }
        vo.setAesKey(defaultAesKey);
        vo.setRequestType(BasicConstant.TOKEN_REQUEST_TYPE);
        vo.setUrl(dto.getUrl());
        return vo;
    }

    /**
     * token验签
     */
    @Override
    public OrganCheckSignVo checkSignByToken(BasicDto dto) {
        String token = dto.getToken();
        if (StringUtils.isBlank(token)) {
            throw new BaseException(ErrorCodeEnums.TOKEN_NOT_NULL);
        }
        //校验token
        String admId = null;
        String jwtKey = null;
        try {
            Claims claims = JwtUtil.decode(token, BasicConstant.MANAGE_TOKEN_KEY);
            if (Objects.nonNull(claims)) {
                if (Objects.nonNull(claims.get(BasicConstant.MANAGE_JWT_KEY))) {
                    admId = claims.get(BasicConstant.MANAGE_JWT_KEY).toString();
                }
                if (Objects.nonNull(claims.get(BasicConstant.MANAGE_JWT_AES_KEY))) {
                    jwtKey = claims.get(BasicConstant.MANAGE_JWT_AES_KEY).toString();
                }
            }
        } catch (Exception e) {
            log.error("token校验失败",e);
            throw new BaseException(ErrorCodeEnums.LOGIN_TOKEN_OVERDUE.getErrcode(),"token校验失败(超时)");
        }
        if (Objects.isNull(admId)) {
            log.error("token校验失败");
            throw new BaseException(ErrorCodeEnums.LOGIN_TOKEN_OVERDUE);
        }
        //从redis获取aesKey
        RBucket<Object> rBucket = redissonUtils.getRBucket(RedisConstants.MANAGE_USER_LOGIN_T_CODE + admId);
        if (Objects.isNull(rBucket) || Objects.isNull(rBucket.get())) {
            throw new BaseException(ErrorCodeEnums.LOGIN_TOKEN_OVERDUE);
        }
        String aesKey = (String) rBucket.get();
        if (!aesKey.equals(jwtKey)) {
            throw new BaseException(ErrorCodeEnums.LOGIN_TOKEN_OVERDUE);
        }

        Admin admin = adminService.getById(admId);
        if (Objects.isNull(admin)) {
            throw new BaseException(ErrorCodeEnums.APP_STATUS_ABNORMAL);
        }
        if (!AdminTypeEnum.TYPE_1.getValue().equals(admin.getAdmType())) {
            throw new BaseException(ErrorCodeEnums.APP_STATUS_ABNORMAL);
        }
        //只需要登录认证,不需要鉴权的url
        boolean isAuth = Optional.ofNullable(manageConfig.getIgnoreAuthUrls()).orElse(new ArrayList<>()).stream().anyMatch(ignoreUrl -> ignoreUrl.equals(dto.getUrl()));
        if (!isAuth) {
            //校验权限
            List<AdminBindRole> adminBindRoles = adminBindRoleService.selectList(admin.getAdmId());
            if (CollectionUtils.isEmpty(adminBindRoles)) {
                throw new BaseException(ErrorCodeEnums.NOT_AUTH);
            }
            List<Long> admRoleIds = adminBindRoles.stream().map(AdminBindRole::getAbrRoleId).collect(Collectors.toList());
            List<AdminRoleAuth> adminRoleAuths = adminRoleAuthService.selectAdminRoleAuthByRoleIds(admRoleIds);
            if (CollectionUtils.isEmpty(adminRoleAuths)) {
                throw new BaseException(ErrorCodeEnums.NOT_AUTH);
            }
            Set<Long> idSet = new HashSet<>();
            for (AdminRoleAuth adminRoleAuth : adminRoleAuths) {
                Long araAuthId = adminRoleAuth.getAraAuthId();
                idSet.add(araAuthId);
            }
            List<AdminAuth> adminAuths = adminAuthService.listByIds(idSet);
            if (CollectionUtils.isEmpty(adminAuths)) {
                throw new BaseException(ErrorCodeEnums.NOT_AUTH);
            }
            Set<String> urlSet = new HashSet<>();
            for (AdminAuth adminAuth : adminAuths) {
                if (StringUtils.isNotBlank(adminAuth.getAuthUrl())) {
                    String[] split = adminAuth.getAuthUrl().split(",");
                    Collections.addAll(urlSet, split);
                }
            }
            if (!urlSet.contains(dto.getUrl())) {
                throw new BaseException(ErrorCodeEnums.NOT_AUTH);
            }
        }
        OrganCheckSignVo vo = new OrganCheckSignVo();
        vo.setOrgNo(admin.getAdmOrgNo());
        vo.setAgentNo(admin.getAdmAgentNo());
        vo.setAdmId(admin.getAdmId());
        vo.setAesKey(aesKey);
        vo.setRequestType(BasicConstant.TOKEN_REQUEST_TYPE);
        vo.setUrl(dto.getUrl());
        if (StringUtils.isNotBlank(dto.getData())) {
            if (StringUtils.isBlank(dto.getSign())) {
                throw new BaseException(ErrorCodeEnums.SIGN_NOT_NULL);
            }
            //验签
            LoginCrypto loginCrypto = JmSwitch.getLoginCrypto(dto.getSign_type());
            boolean checkSign = loginCrypto.verifySign(dto);
            if (!checkSign) {
                throw new BaseException(ErrorCodeEnums.SIGN_ERROR);
            }
            //解密数据
            String data = dto.getData();
            if (StringUtils.isNotBlank(data)) {
                try {
                    vo.setData(loginCrypto.decrypt(data, aesKey));
                } catch (BaseException e) {
                    throw e;
                } catch (Exception e) {
                    log.error("系统异常: ", e);
                    throw new BaseException("数据解密失败:" + e.getMessage());
                }
            }
        }
        return vo;
    }

    @Override
    public boolean ignoreEncryptData(String url) {
        if (StringUtils.isBlank(url)) {
            return false;
        }
        boolean isAuth = Optional.ofNullable(manageConfig.getIgnoreAuthUrls()).orElse(new ArrayList<>()).stream().anyMatch(ignoreUrl -> url.startsWith(ignoreUrl));
        return isAuth;
    }


    @Override
    public OrganEncryptVo encryptData(OrganEncryptDto encryptDataDto) {
        if (BasicConstant.TOKEN_REQUEST_TYPE.equals(encryptDataDto.getRequestType())) {
            //token方式请求
            return encryptDataByToken(encryptDataDto);
        }
        OrganEncryptVo vo = new OrganEncryptVo();
        TreeMap<String, String> returnData = new TreeMap<>();
        returnData.put("errcode", "0");
        returnData.put("msg", "ok");
        String aesKey = encryptDataDto.getAesKey();
        if (StringUtils.isBlank(aesKey)) {
            throw new BaseException("签名参数aesKey错误");
        }
        String privateKey = encryptDataDto.getOrgPrivateKey();
        if (StringUtils.isBlank(privateKey)) {
            throw new BaseException("签名参数privateKey错误");
        }
        //根据 sign_type 获取加解密工具对象
        LoginCrypto loginCrypto = JmSwitch.getLoginCrypto(encryptDataDto.getSign_type());
        try {
            String data = loginCrypto.encrypt(encryptDataDto.getData(), aesKey);
            returnData.put("data", data);
            String sortedStr = BasicUtils.sort(returnData);
            String sha1 = BasicUtils.SHAL1(sortedStr);

            // String sign = RsaUtils.rsaSignByPrivateKey(sha1, privateKey);
            String sign = loginCrypto.signByPrivateKey(sha1, privateKey);
            vo.setErrcode(ErrorCodeEnums.SUCCESS.getErrcode());
            vo.setMsg(ErrorCodeEnums.SUCCESS.getMsg());
            vo.setData(data);
            vo.setSign(sign);
        } catch (Exception e) {
            throw new BaseException(e.getMessage());
        }
        return vo;
    }

    private OrganEncryptVo encryptDataByToken(OrganEncryptDto encryptDataDto) {
        LoginCrypto loginCrypto = JmSwitch.getLoginCrypto(encryptDataDto.getSign_type());
        OrganEncryptVo vo = new OrganEncryptVo();
        TreeMap<String, String> returnData = new TreeMap<>();
        returnData.put("errcode", "0");
        returnData.put("msg", "ok");
        String aesKey = encryptDataDto.getAesKey();
        if (StringUtils.isBlank(aesKey)) {
            throw new BaseException("签名参数aesKey错误");
        }
        try {
            String data = loginCrypto.encrypt(encryptDataDto.getData(), aesKey);
            returnData.put("data", data);
            String sortedStr = BasicUtils.sort(returnData);
            String sha1 = BasicUtils.SHAL1(sortedStr);

            String sign = loginCrypto.messageDigest(sha1);
            vo.setErrcode(ErrorCodeEnums.SUCCESS.getErrcode());
            vo.setMsg(ErrorCodeEnums.SUCCESS.getMsg());
            vo.setData(data);
            vo.setSign(sign);
        } catch (Exception e) {
            throw new BaseException(e.getMessage());
        }
        return vo;
    }

    @Override
    public boolean ignoreAuthentication(String url) {
        boolean isAuth = Optional.ofNullable(gatewayConfig.getIgnoreUrl()).orElse(new ArrayList<>()).stream().anyMatch(ignoreUrl -> {
                    if (ignoreUrl.equals(url)) {
                        return true;
                    }
                    if (ignoreUrl.contains("*")) {
                        return  isMatch(url,ignoreUrl);
                    }
                    return false;
                }
        );
        return isAuth;
    }
    @Override
    public boolean ignoreEncryptUrl(String url) {
        boolean isAuth = Optional.ofNullable(gatewayConfig.getIgnoreEncryptUrl()).orElse(new ArrayList<>()).stream().anyMatch(ignoreUrl -> {
                    if (ignoreUrl.equals(url)) {
                        return true;
                    }
                    if (ignoreUrl.contains("*")) {
                        return  isMatch(url,ignoreUrl);
                    }
                    return false;
                }
        );
        return isAuth;
    }

    static   boolean isMatch(String s, String p) {
        // i 用来记录s串检测的索引的位置
        int i = 0;
        // j 用来记录p串检测的索引的位置
        int j = 0;
        // 记录 待测串i的回溯点
        int ii = -1;
        // 记录 通配符*的回溯点
        int jj = -1;

        // 以s字符串的长度为循环基数，用i来记录s串当前的位置
        while (i < s.length()) {
            // 用j来记录p串的当前位置，检测p串中j位置的值是不是通配符*
            if (j < p.length() && p.charAt(j) == '*') {
                // 如果在p串中碰到通配符*，复制两串的当前索引，记录当前的位置，并对p串+1，定位到非通配符位置
                ii = i;
                jj = j;
                j++;
            }
            else if (j < p.length() // 检测p串是否结束
                    && (s.charAt(i) == p.charAt(j)   // 检测两串当前位置的值是否相等
                    || p.charAt(j) == '?')) { // 检测p串中j位置是否是单值通配符？
                // 如果此时p串还在有效位置上，那么两串当前位置相等或者p串中是单值通配符，表明此时匹配通过，两串均向前移动一步
                i++;
                j++;
            }
            else {
                // 如果在以上两种情况下均放行，表明此次匹配是失败的，那么此时就要明确一点，s串是否在被p串中的通配符*监听着，
                // 因为在首次判断中如果碰到通配符*，我们会将他当前索引的位置记录在jj的位置上，
                // 如果jj = -1 表明匹配失败，当前s串不在监听位置上
                if (jj == -1) return false;
                // 如果此时在s串在通配符*的监听下， 让p串回到通配符*的位置上继续监听下一个字符
                j = jj;
                // 让i回到s串中与通配符对应的当前字符的下一个字符上，也就是此轮匹配只放行一个字符
                i = ii + 1;
            }
        }

        // 当s串中的每一个字符都与p串中的字符进行匹配之后，对p串的残余串进行检查，如果残余串是一个*那么继续检测，否则跳出
        while (j < p.length() && p.charAt(j) == '*') j++;

        // 此时查看p是否已经检测到最后，如果检测到最后表示匹配成功，否则匹配失败
        return j == p.length();
    }
}
